Unity-Shader Graph

学习自 B 站 UP 主游戏石匠Super。

资源

课程

最简明的图形渲染流程解说——别被图形学吓到,入门

图形渲染流程:

几何阶段

  • 模型

    • 模型顶点 XYZXYZ 坐标

    • 模型矩阵 MM

  • 世界(游戏场景)

    • 世界空间坐标
      • 模型空间到世界空间的转换过程:世界空间坐标 =M×= M\times 顶点坐标
  • 相机

    • 观察矩阵 VV
    • 观察空间坐标
      • 世界空间到观察空间的转换过程:观察空间坐标 =V×M×= V\times M\times 顶点坐标
  • 投影

    • 投影矩阵 PP
    • 投影坐标
      • 观察空间到投影的转换过程:投影坐标 =P×V×M×= P\times V\times M\times 顶点坐标

由于矩阵乘法满足结合律,因此我们将引入的三个矩阵结合称为 MVPMVP 矩阵。(MVPMVP 矩阵乘以坐标,就完成了坐标的完整变化,这个过程被称为几何阶段

要对顶点坐标做额外的处理,称之为顶点着色器 Vertex Shader


光栅化

几何阶段结束后,还需要进行光栅化,才可以一个个像素点地显示。

在光栅化阶段,计算机需要知道这些屏幕像素需要涂上什么样的颜色,是片元着色器 Fragment Shader 需要完成的工作。


通用着色器又叫顶点-片元着色器,因此两种着色器加起来才是完整的 Shader。

01. 第一个光照 shader

URP / HDRP 支持 ShaderGraph。

  • Lit Shader Graph,光照模板,相当于以前的 PBR 节点。

  • Unlit Shader Graph,无光照模板

png

新建一个 Lit Shader Graph,打开之。

png
  • 左边的面板可以控制最终显示在 Material 面板上的参数。

  • 中间的节点控制 Shader 逻辑。

  • 右边的 Graph Inspector 用于设置节点属性(Node Settings)和全局属性(Graph)。

课程的示例 Shader Graph。

  • MainTex 是可设置的 Texture2D 类。
  • Sample Texture 2D 节点将 Texture 坐标转成 RGBA 颜色信息输出。
  • MainColor 是可设置的 Color 类。
  • Multiply 节点将两种颜色 A 和 B 相乘成一种颜色 Out 输出。
  • ColorTemp 和 ColorTint 都是可设置的 Float 数,在 Node Settings 以 Slider 形式,范围在 0 到 1 之间。
    • ColorTemp 色温越小越蓝,越大越黄。
    • ColorTint 色调越小越绿,越大越粉。
  • White Balance 节点用于白平衡处理。
  • 最后将输出的颜色应用在 Fragment 的 BaseColor 上。

在 GameObject 上的效果:

png

01-1 光照补充与 SubGraph

png

新建一个 Shader Graph,绑定 Material,再绑定到模型上。

jpg

一阵操作 Shader Graph,使得 Shader Graph 可以支持:

  • MainTex 贴图
  • Normal 法线贴图
  • Matallic 金属度贴图
  • Smoothness 光滑度
  • Emission 自发光贴图
  • EmissionOn 控制自发光是否打开
  • EmissionColor 自发光颜色
  • AO 环境光屏蔽贴图

选中这些节点,右键 Covert To Sub Graph。

png

这些节点就以一个 SubGraph 的形式展示。

02. 轮廓光/边缘光

png

根据原模型提供的漫反射贴图 MainTex、法线贴图 NormalTex、金属度贴图 MetalTex、自发光贴图 EmissionTex 创建 Shader Graph 节点。

png

创建轮廓光效果:

  • Fresnel Effect 节点,创建轮廓光
  • RimPower,Float 属性,控制轮廓光强度,值越接近 0,轮廓光越发散
  • RimColor,Color 属性,控制轮廓光颜色
  • Multiply 节点,将 Fresnel Effect 节点输出的颜色值和 RimColor 相乘,获得带颜色的轮廓光
  • Add 节点,将轮廓光与轮廓光贴图相结合
    • 为什么要用 Add 而不是 Multiply?因为轮廓光贴图几乎全黑,使用 Multiply 会导致最后输出也几乎全黑
png

最终效果。

03. 溶解

png

溶解效果需要在 Graph Inspector 里打开 Alpha Cliping。

  • Simple Noise 节点控制溶解形态,连接至 Alpha。

  • Noise Scale 控制溶解图案大小。

  • ClipRate 控制 Alpha Clip Threshold,Alpha 值低于 ClipRate 的点将不会被渲染。

png

添加溶解自发光边缘。

  • EdgeWidth 控制边缘宽度。
  • ClipRate 和 EdgeWitdh 经过 Add 节点叠加,输出值和 Simple Noise 的输出作为 Step 节点的输入,得到一张二值化图。
  • 二值化图和 EdgeColor 节点作为 Multiply 节点的输入,给溶解边缘染色,最后连上 Emission。
png

演示效果。

png

如果要利用模型自带的 Emission 贴图,还要再整个 Add 节点,最后再连 Emission 节点。

png

可以使用代码控制 ClipRate 的值来控制消融状态。

如果要让物体自动消融,则可以使用 Time 节点连着 Remap 替换 ClipRate 来让物体自动消融。

04. 水面(上)

png
  • 水面是透明的,Surface Type 设成 Transparent。

  • 水面没有阴影,关闭 Cast Shadows。

png

检测与岸边的相交边缘,不透明的物体与半透明的物体之间的边缘检测,需要依靠屏幕深度来完成,打开它。

png

场景深度节点深度(Scene Depth,Sampling 为 Eye,包含透明像素)减去屏幕空间的 WW 向量(Screen Position,Mode 为 Raw,不包含透明像素)就是不透明物体与半透明物体相交的边缘。

png

预览结果。靠近水面边缘的地方呈现黑色,表明场景深度有效。

png
  • 增加一个 Depth,控制深浅强度。

  • Strength 连 Multiply 调整修改力度。

png
  • Clamp 将值钳制在 0 到 1 之间。
  • ShallowColor 和 Deep Color 控制水波颜色。
png

浏览效果,此时水已经被染色。

png

Lerp 分理出 Alpha 通道,以显示水的透明效果。

设定好 ShallowColor 和 Deep Color 的 Alpha 值才有效。

png

浏览效果,此时水呈现透明色。

png

给水增加法线贴图。

共有两种法线:

  • FirstNormal
  • SecondNormal

SampleTexture2D 的 Type 记得选 Normal。两个法线贴图用 Add 节点相加。

  • Tiling And Offset 控制法线贴图的缩放和平移。
  • Normal Strength 调整法线强度,最后连片元着色器的 Normal。
png

浏览效果。可以正确渲染法线了。

png

将 NormalStrength 与 边缘信息再进行 Lerp 运算,使得法线贴图考虑边缘信息。

png

给法线贴图的 Tiling And Offset 用 Time 控制,以达到水波流动的动态效果。

png

在 Scene 里打开 Always Refresh,便可浏览到水流实时流动的效果。

png

接下来修改顶点着色器,只需要修改模型的 Y 坐标即可。

用 Gradient Noise 给模型的 Y 坐标做一个扰动,后面用 Time、Multiply、Tiling And Offset 控制扰动属性。

png

浏览效果,此时物体发生了形变。

png

增加 Displacement 控制扰动强度。

png

预览结果。通过修改 Displacement 的值以修改扰动强度。

05. 水面(下)

png

为了给水面增加波光粼粼的效果,使用一个 Float 类型,范围 [0,1][0, 1] 的 Smoothness 来控制片元着色器的 Smoothness。

png

给水面增加物体折射扭曲的效果,需要操作场景像素点的颜色 Scene Color,渲染器设置的 Opaque Texture 需要打开。

png

增加 Gradient Noise,Normal From Weight 将其转成法线贴图来调整 Scene Color,最后与之前渲染的颜色作一个 Lerp,输出到片元着色器的 Base Color。

RefractionStrength 可以调整折射的强度。

png

预览效果。

png

使用 Time 操作 Gradient Noise 的 Tiling And Offset 让折射效果随时间而变化。

RefractionSpeed 和 RefractionScale 分别控制折射效果的改变速度和扭曲大小。

png

最终 Shader Graph 如上图所示。

06. 积雪

png

使用之前 pbr Sub Graph 创建一个基本的 Shader Graph 模板。

png

通过物体表面法线方向 Normal Vector 与世界空间的夹角来判断这个表面是否应该要有积雪。

  • SnowDirection 雪的方向,作一个 Normalize 归一化。
  • DotProduction,计算物体表面法线方向和雪的法线之间的夹角。
  • Remap,将 [1,1][-1, 1] 的范围映射到 [0,1][0, 1]
  • SnowDepth,控制雪的深度。
  • OneMinus,输出值 = 1 - 输出值,让 SnowDepth 符合值越大深度越深的效果。
  • Step,阈值处理。
  • 与之前的 Emission 做相加,最后的 Emission 即为所求。
png

预览效果。

07. 自定义光照

漫反射:

兰伯特光照模型:diffuse=I(LN)diffuse=I\cdot (L\cdot N)

  • II 是光照强度。
  • LL 为入射光线的反向量。
  • NN 为当前表面的法向量方向。
png

这么放置节点。

png

MainLight 是一个自定义函数类,设置好它的 Outputs:

  • Color 漫反射颜色
  • Direction 漫反射方向
glsl
# if SHADERGRAPH_PREVIEW
Color = 1;
Direction = -1;
# else
Light light = GetMainLight();
Color = light.color;
Direction = light.direction;
# endif
png

漫反射预览效果。

glsl
void MainLight_half(float3 WorldPos, out half3 Direction, out half3 Color, out half DistanceAtten, out half ShadowAtten)
{
#ifdef SHADERGRAPH_PREVIEW
    Direction = half3(0.5, 0.5, 0);
    Color = 1;
    DistanceAtten = 1;
    ShadowAtten = 1;
#else
 
#ifdef SHADOWS_SCREEN
    half4 clipPos = TransformWorldToClip(WorldPos);
    half4shadowCoord = ComputeScreenPos(clipPos);
#else
    half4shadowCoord = TransformWorldToShadowCoord(WorldPos);
#endif
    Light light = GetMainLight(shadowCoord);
    Direction = light.direction;
    Color = light.color;
    DistanceAtten = light.distanceAttenuation;
    ShadowAtten = light.shadowAttenuation;
#endif
}
 
void DirectSpecular_half(half3 Specular, half Smoothness, half3 Direction, half3 Color, half3 WorldNormal, half3 WorldView, out half3 Out)
 
{
 
#ifdef SHADERGRAPH_PREVIEW
 
    Out = 0;
 
#else
 
    Smoothness = exp2(10 * Smoothness + 1);
 
    WorldNormal = normalize(WorldNormal);
 
    WorldView = SafeNormalize(WorldView);
 
    Out = LightingSpecular(Color, Direction, WorldNormal, WorldView, half4(Specular, 0), Smoothness);
 
#endif
}

这个 HLSL 文件中定义了两个函数,MainLight_halfDirectSpecular_half。这两个函数分别用于计算主光源的信息直接镜面反射的信息

MainLight_half 函数接收一个世界坐标位置WorldPos)作为输入,输出主光源的方向Direction)、颜色Color)、距离衰减DistanceAtten)和阴影衰减ShadowAtten)。函数中首先判断是否在 ShaderGraph 预览模式下,如果是则直接赋值,否则根据是否使用屏幕空间阴影来计算阴影坐标shadowCoord),然后根据阴影坐标获取主光源信息

DirectSpecular_half 函数接收镜面反射系数Specular)、光滑度Smoothness)、方向Direction)、颜色Color)、世界法线WorldNormal)和世界视线WorldView)作为输入,输出镜面反射的颜色Out)。函数中首先判断是否在 ShaderGraph 预览模式下,如果是则直接赋值,否则对输入参数进行处理(例如计算光滑度,规范化法线和视线等),然后调用 LightingSpecular 函数计算镜面反射的颜色。

这两个函数主要用于在 Unity 的 Shader 中计算光照和反射,以实现更真实的渲染效果。

png

把上面这些代码放在一个 hlsl 文件中。

png

MainLight_half 函数,精度为 Half,读取 Postion 作为世界坐标,输出:

  • 主光源的方向 Direction
  • 颜色 Color
  • 距离衰减 DistanceAtten
  • 阴影衰减 ShadowAtten

镜面反射:光照强度是光源向量与顶点法线的反射向量的点积。

png

DirectSpecular_half 函数,精度为 Half。

输入:

  • 镜面反射系数 Specular
  • 光滑度 Smoothness
  • 方向 Direction
  • 颜色 Color
  • 世界法线 WorldNormal
  • 世界视线 WorldView

输出:

  • 镜面反射的颜色 Out
png
  • 镜面反射系数 Specular
  • 光滑度 Smoothness
  • 方向 Direction
  • 颜色 Color

由物体给出;

  • 世界法线 WorldNormal
  • 世界视线 WorldView

从游戏世界中获取。

png

emmmm 乱七八糟的节点。

png

最终效果。

08. 四方线框

png

教程里的四方线框对模型的 UV 贴图有要求,Blender 新建一个立方体模型,做细分,添加一个 UVMaps 并重置,导出至 Unity。

png

使用 Alpha Clip 隐去非边框的像素来实现四方线框的效果。

png

预览效果。

png

制作 U 另一方向。

png

预览效果。

png

同理,制作 V 方向的效果。

png

预览效果。

png

增加 Emission 颜色,颜色模式设为 HDR。

png

预览效果。

png

如果需要辉光效果,在 Camera 里打开 Post Processing。

png

Volume 调整 Bloom 的 Tint 和 Intensity。

png

制作线框消失的效果,将顶点低于 Threshold 的隐去。

png

预览效果。

png

用 Time 代替 Threshold,由于 Time 是一个随时间不断增加的数,对其进行 Modulo 求余就可以实现周期一样的效果。

09. Shader 帧动画

png

使用 Shader Graph 来实现用一张 UV 贴图的帧动画。

png

使用 Tiling And Offset 将 UV 贴图裁切。

png

一阵数学运算使得可以根据坐标来裁切 UV 贴图。

png

使用 Time 使得自动播放 UV 动画。

png

全家福。